package health_system.health_system.service.impl;
import health_system.health_system.entity.Friendship;
import health_system.health_system.entity.PrivacySettings;
import health_system.health_system.service.FriendshipService;

import health_system.health_system.dto.FriendshipDTO;
import health_system.health_system.dto.FriendshipUpdateDTO;
import health_system.health_system.dto.PageResponseDTO;
import health_system.health_system.dto.UserDTO;
import health_system.health_system.entity.Friendship.*;
import health_system.health_system.entity.User;
import health_system.health_system.exception.BusinessException;
import health_system.health_system.exception.FriendshipAlreadyExistsException;
import health_system.health_system.exception.FriendshipNotFoundException;
import health_system.health_system.exception.UserNotFoundException;
import health_system.health_system.repository.FriendshipRepository;
import health_system.health_system.repository.UserRepository;
import health_system.health_system.service.FriendshipService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

@Slf4j
@Service
@RequiredArgsConstructor
public class FriendshipServiceImpl implements FriendshipService {

    private final FriendshipRepository friendshipRepository;
    private final UserRepository userRepository;

    @Override
    @Transactional
    @Caching(evict = {
        @CacheEvict(value = "friendships", allEntries = true),
        @CacheEvict(value = "friendshipsByUser", allEntries = true),
        @CacheEvict(value = "acceptedFriendshipsByUser", allEntries = true),
        @CacheEvict(value = "friendsByUser", allEntries = true),
        @CacheEvict(value = "pendingFriendRequests", allEntries = true)
    })
    public FriendshipDTO createFriendship(FriendshipDTO friendshipDTO) {
        log.debug("Creating friendship request based on DTO: {}", friendshipDTO);
        
        // Check if requester and addressee are the same
        if (friendshipDTO.getRequester().getId().equals(friendshipDTO.getAddressee().getId())) {
            throw new BusinessException("Cannot create friendship with yourself");
        }
        
        // Get requester entity
        User requester = userRepository.findById(friendshipDTO.getRequester().getId())
                .orElseThrow(() -> new UserNotFoundException("requester", friendshipDTO.getRequester().getId()));
        
        // Get addressee entity
        User addressee = userRepository.findById(friendshipDTO.getAddressee().getId())
                .orElseThrow(() -> new UserNotFoundException("addressee", friendshipDTO.getAddressee().getId()));
        
        // Check if friendship already exists
        Optional<Friendship> existingFriendship = friendshipRepository.findFriendshipBetweenUsers(requester, addressee);
        
        Friendship friendship;
        
        if (existingFriendship.isPresent()) {
            // Update existing friendship instead of throwing exception
            log.info("Friendship already exists between users {} and {}. Updating instead of creating new.", 
                    friendshipDTO.getRequester().getId(), friendshipDTO.getAddressee().getId());
            
            friendship = existingFriendship.get();
            
            // Only update status if it's not already ACCEPTED
            if (friendship.getStatus() != FriendshipStatus.ACCEPTED) {
                friendship.setStatus(friendshipDTO.getStatus() != null ? 
                                friendshipDTO.getStatus() : Friendship.FriendshipStatus.PENDING);
            }
            
            // Update notes if provided
            if (friendshipDTO.getRequestRemark() != null && !friendshipDTO.getRequestRemark().isEmpty()) {
                friendship.setRequesterNote(friendshipDTO.getRequestRemark());
            } else if (friendshipDTO.getRequesterNote() != null && !friendshipDTO.getRequesterNote().isEmpty()) {
                friendship.setRequesterNote(friendshipDTO.getRequesterNote());
            }
            
            if (friendshipDTO.getAddresseeNote() != null && !friendshipDTO.getAddresseeNote().isEmpty()) {
                friendship.setAddresseeNote(friendshipDTO.getAddresseeNote());
            }
            
            if (friendshipDTO.getFriendCircle() != null && !friendshipDTO.getFriendCircle().isEmpty()) {
                friendship.setFriendCircle(friendshipDTO.getFriendCircle());
            }
            
            // Update privacy settings if provided
            if (friendshipDTO.getPrivacySettings() != null) {
                PrivacySettings privacySettings = friendship.getPrivacySettings();
                if (privacySettings == null) {
                    privacySettings = new PrivacySettings();
                    privacySettings.setFriendship(friendship);
                    friendship.setPrivacySettings(privacySettings);
                }
                
                privacySettings.setMoments(friendshipDTO.getPrivacySettings().isMoments());
                privacySettings.setLocation(friendshipDTO.getPrivacySettings().isLocation());
                privacySettings.setProfileInfo(friendshipDTO.getPrivacySettings().isProfileInfo());
                privacySettings.setLastSeen(friendshipDTO.getPrivacySettings().isLastSeen());
                privacySettings.setPhone(friendshipDTO.getPrivacySettings().isPhone());
                privacySettings.setEmail(friendshipDTO.getPrivacySettings().isEmail());
            }
        } else {
            // Create new friendship
            friendship = new Friendship();
            friendship.setRequester(requester);
            friendship.setAddressee(addressee);
            friendship.setStatus(friendshipDTO.getStatus() != null ? 
                                friendshipDTO.getStatus() : Friendship.FriendshipStatus.PENDING);
            
            // 处理可能的requestRemark字段
            if (friendshipDTO.getRequestRemark() != null && (friendshipDTO.getRequesterNote() == null || friendshipDTO.getRequesterNote().isEmpty())) {
                friendship.setRequesterNote(friendshipDTO.getRequestRemark());
            } else {
                friendship.setRequesterNote(friendshipDTO.getRequesterNote());
            }
            
            friendship.setAddresseeNote(friendshipDTO.getAddresseeNote());
            friendship.setFriendCircle(friendshipDTO.getFriendCircle());
            
            // Handle privacy settings if provided
            if (friendshipDTO.getPrivacySettings() != null) {
                PrivacySettings privacySettings = new PrivacySettings();
                privacySettings.setFriendship(friendship);
                privacySettings.setMoments(friendshipDTO.getPrivacySettings().isMoments());
                privacySettings.setLocation(friendshipDTO.getPrivacySettings().isLocation());
                privacySettings.setProfileInfo(friendshipDTO.getPrivacySettings().isProfileInfo());
                privacySettings.setLastSeen(friendshipDTO.getPrivacySettings().isLastSeen());
                privacySettings.setPhone(friendshipDTO.getPrivacySettings().isPhone());
                privacySettings.setEmail(friendshipDTO.getPrivacySettings().isEmail());
                friendship.setPrivacySettings(privacySettings);
            } else {
                // Create default privacy settings
                PrivacySettings privacySettings = new PrivacySettings();
                privacySettings.setFriendship(friendship);
                friendship.setPrivacySettings(privacySettings);
            }
        }
        
        try {
            Friendship savedFriendship = friendshipRepository.save(friendship);
            log.info("Successfully saved friendship with ID: {}", savedFriendship.getId());
            return FriendshipDTO.fromEntity(savedFriendship);
        } catch (DataIntegrityViolationException e) {
            log.error("Failed to save friendship due to data integrity violation", e);
            throw new BusinessException("Failed to save friendship. Please check your input data.", e);
        }
    }

    @Override
    @Transactional
    @Caching(evict = {
        @CacheEvict(value = "friendships", key = "#id"),
        @CacheEvict(value = "friendshipsByUser", allEntries = true),
        @CacheEvict(value = "acceptedFriendshipsByUser", allEntries = true),
        @CacheEvict(value = "friendsByUser", allEntries = true)
    })
    public FriendshipDTO acceptFriendship(Long id) {
        log.debug("Accepting friendship with ID: {}", id);
        
        Friendship friendship = getFriendshipEntityById(id);
        
        // Only the addressee can accept a friendship request
        if (friendship.getStatus() != Friendship.FriendshipStatus.PENDING) {
            throw new BusinessException("The friendship is not in a pending state");
        }
        
        // Change status to accepted
        friendship.setStatus(Friendship.FriendshipStatus.ACCEPTED);
        friendship.setUpdatedAt(LocalDateTime.now());
        
        try {
            Friendship acceptedFriendship = friendshipRepository.save(friendship);
            log.info("Successfully accepted friendship with ID: {}", id);
            return FriendshipDTO.fromEntity(acceptedFriendship);
        } catch (Exception e) {
            log.error("Failed to accept friendship with ID: {}", id, e);
            throw new BusinessException("Failed to accept friendship", e);
        }
    }

    @Override
    @Transactional
    @Caching(evict = {
        @CacheEvict(value = "friendships", key = "#id"),
        @CacheEvict(value = "friendshipsByUser", allEntries = true),
        @CacheEvict(value = "acceptedFriendshipsByUser", allEntries = true),
        @CacheEvict(value = "friendsByUser", allEntries = true)
    })
    public FriendshipDTO rejectFriendship(Long id) {
        log.debug("Rejecting friendship with ID: {}", id);
        
        Friendship friendship = getFriendshipEntityById(id);
        
        // Verify the friendship is in PENDING status
        if (friendship.getStatus() != Friendship.FriendshipStatus.PENDING) {
            throw new BusinessException("Only pending friendship requests can be rejected");
        }
        
        // Update friendship
        friendship.setStatus(FriendshipStatus.REJECTED);
        
        try {
            Friendship updatedFriendship = friendshipRepository.save(friendship);
            log.info("Successfully rejected friendship with ID: {}", updatedFriendship.getId());
            return FriendshipDTO.fromEntity(updatedFriendship);
        } catch (Exception e) {
            log.error("Failed to reject friendship with ID: {}", id, e);
            throw new BusinessException("Failed to reject friendship", e);
        }
    }

    @Override
    @Transactional
    @Caching(evict = {
        @CacheEvict(value = "friendships", allEntries = true),
        @CacheEvict(value = "friendshipsByUser", allEntries = true),
        @CacheEvict(value = "acceptedFriendshipsByUser", allEntries = true),
        @CacheEvict(value = "friendsByUser", allEntries = true)
    })
    public FriendshipDTO blockUser(Long userId, Long targetUserId) {
        log.debug("Blocking user ID: {} by user ID: {}", targetUserId, userId);
        
        // Check if the users are the same
        if (userId.equals(targetUserId)) {
            throw new BusinessException("Cannot block yourself");
        }
        
        // Get user entities
        User user = userRepository.findById(userId)
                .orElseThrow(() -> new UserNotFoundException(userId));
        
        User targetUser = userRepository.findById(targetUserId)
                .orElseThrow(() -> new UserNotFoundException(targetUserId));
        
        // Find existing friendship
        Optional<Friendship> existingFriendship = 
                friendshipRepository.findFriendshipBetweenUsers(user, targetUser);
        
        Friendship friendship;
        
        if (existingFriendship.isPresent()) {
            friendship = existingFriendship.get();
            friendship.setStatus(FriendshipStatus.BLOCKED);
        } else {
            // Create new blocked relationship
            friendship = new Friendship();
            
            // The user who blocks is always the requester in a newly created block relationship
            friendship.setRequester(user);
            friendship.setAddressee(targetUser);
            friendship.setStatus(FriendshipStatus.BLOCKED);
        }
        
        try {
            Friendship savedFriendship = friendshipRepository.save(friendship);
            log.info("Successfully blocked user with ID: {}", targetUserId);
            return FriendshipDTO.fromEntity(savedFriendship);
        } catch (Exception e) {
            log.error("Failed to block user with ID: {}", targetUserId, e);
            throw new BusinessException("Failed to block user", e);
        }
    }

    @Override
    @Transactional
    @Caching(evict = {
        @CacheEvict(value = "friendships", allEntries = true),
        @CacheEvict(value = "friendshipsByUser", allEntries = true),
        @CacheEvict(value = "acceptedFriendshipsByUser", allEntries = true),
        @CacheEvict(value = "friendsByUser", allEntries = true)
    })
    public FriendshipDTO unblockUser(Long userId, Long targetUserId) {
        log.debug("Unblocking user ID: {} by user ID: {}", targetUserId, userId);
        
        // Get user entities
        User user = userRepository.findById(userId)
                .orElseThrow(() -> new UserNotFoundException(userId));
        
        User targetUser = userRepository.findById(targetUserId)
                .orElseThrow(() -> new UserNotFoundException(targetUserId));
        
        // Find existing friendship
        Optional<Friendship> existingFriendship = 
                friendshipRepository.findFriendshipBetweenUsers(user, targetUser);
        
        if (!existingFriendship.isPresent() || existingFriendship.get().getStatus() != FriendshipStatus.BLOCKED) {
            throw new BusinessException("No blocked relationship found between the users");
        }
        
        Friendship friendship = existingFriendship.get();
        
        // Change status to pending
        friendship.setStatus(FriendshipStatus.PENDING);
        
        try {
            Friendship savedFriendship = friendshipRepository.save(friendship);
            log.info("Successfully unblocked user with ID: {}", targetUserId);
            return FriendshipDTO.fromEntity(savedFriendship);
        } catch (Exception e) {
            log.error("Failed to unblock user with ID: {}", targetUserId, e);
            throw new BusinessException("Failed to unblock user", e);
        }
    }

    @Override
    @Transactional
    @CacheEvict(value = "friendships", key = "#id")
    public FriendshipDTO updateFriendship(Long id, FriendshipUpdateDTO updateDTO, Long userId) {
        log.debug("Updating friendship with ID: {} by user ID: {}", id, userId);
        
        Friendship friendship = getFriendshipEntityById(id);
        
        // Verify the user is involved in the friendship
        if (!friendship.getRequester().getId().equals(userId) && !friendship.getAddressee().getId().equals(userId)) {
            throw new BusinessException("User is not involved in this friendship");
        }
        
        // Update fields if provided
        if (updateDTO.getStatus() != null) {
            friendship.setStatus(updateDTO.getStatus());
        }
        
        if (updateDTO.getRequesterNote() != null && friendship.getRequester().getId().equals(userId)) {
            friendship.setRequesterNote(updateDTO.getRequesterNote());
        }
        
        if (updateDTO.getAddresseeNote() != null && friendship.getAddressee().getId().equals(userId)) {
            friendship.setAddresseeNote(updateDTO.getAddresseeNote());
        }
        
        if (updateDTO.getFriendCircle() != null) {
            friendship.setFriendCircle(updateDTO.getFriendCircle());
        }
        
        try {
            Friendship updatedFriendship = friendshipRepository.save(friendship);
            log.info("Successfully updated friendship with ID: {}", updatedFriendship.getId());
            return FriendshipDTO.fromEntity(updatedFriendship);
        } catch (Exception e) {
            log.error("Failed to update friendship with ID: {}", id, e);
            throw new BusinessException("Failed to update friendship", e);
        }
    }

    @Override
    @Transactional(readOnly = true)
    @Cacheable(value = "friendships", key = "#id")
    public FriendshipDTO getFriendshipById(Long id) {
        log.debug("Getting friendship by ID: {}", id);
        
        Friendship friendship = getFriendshipEntityById(id);
        return FriendshipDTO.fromEntity(friendship);
    }
    
    @Transactional(readOnly = true)
    public Friendship getFriendshipEntityById(Long id) {
        log.debug("Getting friendship entity by ID: {}", id);
        
        return friendshipRepository.findById(id)
                .orElseThrow(() -> new FriendshipNotFoundException(id));
    }

    @Override
    @Transactional(readOnly = true)
    public Optional<FriendshipDTO> findFriendshipBetweenUsers(Long userId1, Long userId2) {
        log.debug("Finding friendship between user ID: {} and user ID: {}", userId1, userId2);
        
        User user1 = userRepository.findById(userId1)
                .orElseThrow(() -> new UserNotFoundException(userId1));
        
        User user2 = userRepository.findById(userId2)
                .orElseThrow(() -> new UserNotFoundException(userId2));
        
        Optional<Friendship> friendship = friendshipRepository.findFriendshipBetweenUsers(user1, user2);
        
        return friendship.map(FriendshipDTO::fromEntity);
    }

    @Override
    @Transactional
    @Caching(evict = {
        @CacheEvict(value = "friendships", key = "#id"),
        @CacheEvict(value = "friendshipsByUser", allEntries = true),
        @CacheEvict(value = "acceptedFriendshipsByUser", allEntries = true),
        @CacheEvict(value = "friendsByUser", allEntries = true)
    })
    public void deleteFriendship(Long id) {
        log.debug("Deleting friendship with ID: {}", id);
        
        Friendship friendship = getFriendshipEntityById(id);
        
        try {
            friendshipRepository.delete(friendship);
            log.info("Successfully deleted friendship with ID: {}", id);
        } catch (Exception e) {
            log.error("Failed to delete friendship with ID: {}", id, e);
            throw new BusinessException("Failed to delete friendship", e);
        }
    }

    @Override
    @Transactional(readOnly = true)
    @Cacheable(value = "friendshipsByUser", key = "#userId")
    public List<FriendshipDTO> getAllFriendshipsByUser(Long userId) {
        log.debug("Getting all friendships for user ID: {}", userId);
        
        User user = userRepository.findById(userId)
                .orElseThrow(() -> new UserNotFoundException(userId));
        
        List<Friendship> friendships = friendshipRepository.findAllFriendshipsByUser(user);
        
        if (friendships.isEmpty()) {
            return Collections.emptyList();
        }
        
        return friendships.stream()
                .map(FriendshipDTO::fromEntity)
                .collect(Collectors.toList());
    }

    @Override
    @Transactional(readOnly = true)
    @Cacheable(value = "acceptedFriendshipsByUser", key = "#userId")
    public List<FriendshipDTO> getAcceptedFriendshipsByUser(Long userId) {
        log.debug("Getting accepted friendships for user ID: {}", userId);
        
        User user = userRepository.findById(userId)
                .orElseThrow(() -> new UserNotFoundException(userId));
        
        List<Friendship> friendships = friendshipRepository.findAcceptedFriendshipsByUser(user);
        
        if (friendships.isEmpty()) {
            return Collections.emptyList();
        }
        
        return friendships.stream()
                .map(FriendshipDTO::fromEntity)
                .collect(Collectors.toList());
    }

    @Override
    @Transactional(readOnly = true)
    @Cacheable(value = "acceptedFriendshipsByUserPaged", 
              key = "#userId + '-page:' + #pageable.pageNumber + '-size:' + #pageable.pageSize")
    public PageResponseDTO<FriendshipDTO> getAcceptedFriendshipsByUser(Long userId, Pageable pageable) {
        log.debug("Getting paginated accepted friendships for user ID: {} with page: {}", 
                 userId, pageable.getPageNumber());
        
        User user = userRepository.findById(userId)
                .orElseThrow(() -> new UserNotFoundException(userId));
        
        Page<Friendship> friendshipsPage = friendshipRepository.findAcceptedFriendshipsByUser(user, pageable);
        
        Page<FriendshipDTO> dtoPage = friendshipsPage.map(FriendshipDTO::fromEntity);
        
        return PageResponseDTO.fromPage(dtoPage);
    }

    @Override
    @Transactional(readOnly = true)
    @Cacheable(value = "pendingFriendRequests", key = "#userId")
    public List<FriendshipDTO> getPendingFriendRequests(Long userId) {
        log.debug("Getting pending friend requests for user ID: {}", userId);
        
        User user = userRepository.findById(userId)
                .orElseThrow(() -> new UserNotFoundException(userId));
        
        List<Friendship> friendships = friendshipRepository.findPendingFriendRequestsForUser(user);
        
        if (friendships.isEmpty()) {
            return Collections.emptyList();
        }
        
        return friendships.stream()
                .map(FriendshipDTO::fromEntity)
                .collect(Collectors.toList());
    }

    @Override
    @Transactional(readOnly = true)
    public List<FriendshipDTO> getSentFriendRequestsByUser(Long userId) {
        log.debug("Getting friend requests sent by user ID: {}", userId);
        
        User user = userRepository.findById(userId)
                .orElseThrow(() -> new UserNotFoundException(userId));
        
        List<Friendship> friendships = friendshipRepository.findSentFriendRequestsByUser(user);
        
        if (friendships.isEmpty()) {
            return Collections.emptyList();
        }
        
        return friendships.stream()
                .map(FriendshipDTO::fromEntity)
                .collect(Collectors.toList());
    }

    @Override
    @Transactional(readOnly = true)
    public List<UserDTO> getFriendsByUser(Long userId) {
        log.debug("Getting all friends for user ID: {}", userId);
        
        User user = userRepository.findById(userId)
                .orElseThrow(() -> new UserNotFoundException(userId));
        
        // Get accepted recipients for this user (where user is requester)
        List<User> recipients = friendshipRepository.findAcceptedRecipientsByRequester(user);
        log.debug("Found {} accepted recipients for user {}", recipients.size(), userId);
        
        // Get accepted requesters for this user (where user is addressee)
        List<User> requesters = friendshipRepository.findAcceptedRequestersByRecipient(user);
        log.debug("Found {} accepted requesters for user {}", requesters.size(), userId);
        
        // Combine both lists
        List<User> friends = new ArrayList<>();
        friends.addAll(recipients);
        friends.addAll(requesters);
        
        if (friends.isEmpty()) {
            log.debug("No friends found for user {}", userId);
            return Collections.emptyList();
        }
        
        List<UserDTO> friendDTOs = friends.stream()
                .map(UserDTO::fromEntity)
                .collect(Collectors.toList());
        
        log.debug("Returning {} friend DTOs for user {}", friendDTOs.size(), userId);
        return friendDTOs;
    }

    @Override
    @Transactional(readOnly = true)
    public boolean areFriends(Long userId1, Long userId2) {
        log.debug("Checking if users with IDs {} and {} are friends", userId1, userId2);
        
        User user1 = userRepository.findById(userId1)
                .orElseThrow(() -> new UserNotFoundException(userId1));
        
        User user2 = userRepository.findById(userId2)
                .orElseThrow(() -> new UserNotFoundException(userId2));
        
        return friendshipRepository.areFriends(user1, user2);
    }

    @Override
    @Transactional(readOnly = true)
    public List<FriendshipDTO> getBlockedRelationshipsByUser(Long userId) {
        log.debug("Getting blocked relationships for user ID: {}", userId);
        
        User user = userRepository.findById(userId)
                .orElseThrow(() -> new UserNotFoundException(userId));
        
        List<Friendship> friendships = friendshipRepository.findBlockedRelationshipsByUser(user);
        
        if (friendships.isEmpty()) {
            return Collections.emptyList();
        }
        
        return friendships.stream()
                .map(FriendshipDTO::fromEntity)
                .collect(Collectors.toList());
    }

    @Override
    @Transactional(readOnly = true)
    public List<FriendshipDTO> getFriendshipsByCircle(Long userId, String circleName) {
        log.debug("Getting friendships in circle '{}' for user ID: {}", circleName, userId);
        
        User user = userRepository.findById(userId)
                .orElseThrow(() -> new UserNotFoundException(userId));
        
        List<Friendship> friendships = friendshipRepository.findFriendshipsByCircle(user, circleName);
        
        if (friendships.isEmpty()) {
            return Collections.emptyList();
        }
        
        return friendships.stream()
                .map(FriendshipDTO::fromEntity)
                .collect(Collectors.toList());
    }

    @Override
    @Transactional(readOnly = true)
    public List<UserDTO> getFriendsByCircle(Long userId, String circleName) {
        log.debug("Getting friends in circle '{}' for user ID: {}", circleName, userId);
        
        User user = userRepository.findById(userId)
                .orElseThrow(() -> new UserNotFoundException(userId));
        
        List<User> friends = friendshipRepository.findFriendsByCircle(user, circleName);
        
        if (friends.isEmpty()) {
            return Collections.emptyList();
        }
        
        return friends.stream()
                .map(UserDTO::fromEntity)
                .collect(Collectors.toList());
    }

    @Override
    @Transactional(readOnly = true)
    public List<String> getAllFriendCirclesByUser(Long userId) {
        log.debug("Getting all friend circles for user ID: {}", userId);
        
        User user = userRepository.findById(userId)
                .orElseThrow(() -> new UserNotFoundException(userId));
        
        return friendshipRepository.findAllFriendCirclesByUser(user);
    }

    @Override
    @Transactional(readOnly = true)
    public Long countFriendsByUser(Long userId) {
        log.debug("Counting friends for user ID: {}", userId);
        
        User user = userRepository.findById(userId)
                .orElseThrow(() -> new UserNotFoundException(userId));
        
        return friendshipRepository.countFriendsByUser(user);
    }

    @Override
    @Transactional(readOnly = true)
    public Long countFriendsInCircle(Long userId, String circleName) {
        log.debug("Counting friends in circle '{}' for user ID: {}", circleName, userId);
        
        User user = userRepository.findById(userId)
                .orElseThrow(() -> new UserNotFoundException(userId));
        
        return friendshipRepository.countFriendsInCircle(user, circleName);
    }

    @Override
    @Transactional
    @Caching(evict = {
        @CacheEvict(value = "friendships", key = "#id"),
        @CacheEvict(value = "friendshipsByUser", allEntries = true),
        @CacheEvict(value = "acceptedFriendshipsByUser", allEntries = true),
        @CacheEvict(value = "friendsByUser", allEntries = true)
    })
    public FriendshipDTO updateFriendCircle(Long id, Long userId, String circleName) {
        log.debug("Updating friend circle to '{}' for friendship ID: {} by user ID: {}", 
                 circleName, id, userId);
        
        Friendship friendship = getFriendshipEntityById(id);
        
        // Verify the user is involved in the friendship
        if (!friendship.getRequester().getId().equals(userId) && !friendship.getAddressee().getId().equals(userId)) {
            throw new BusinessException("User is not involved in this friendship");
        }
        
        // Verify the friendship is accepted
        if (friendship.getStatus() != FriendshipStatus.ACCEPTED) {
            throw new BusinessException("Can only update circle for accepted friendships");
        }
        
        // Update circle
        friendship.setFriendCircle(circleName);
        
        try {
            Friendship updatedFriendship = friendshipRepository.save(friendship);
            log.info("Successfully updated friend circle for friendship with ID: {}", 
                    updatedFriendship.getId());
            return FriendshipDTO.fromEntity(updatedFriendship);
        } catch (Exception e) {
            log.error("Failed to update friend circle for friendship with ID: {}", id, e);
            throw new BusinessException("Failed to update friend circle", e);
        }
    }

    @Override
    @Transactional(readOnly = true)
    public List<UserDTO> searchFriendsByName(Long userId, String searchTerm) {
        log.debug("Searching friends of user ID: {} with search term: '{}'", userId, searchTerm);
        
        User user = userRepository.findById(userId)
                .orElseThrow(() -> new UserNotFoundException(userId));
        
        List<User> friends = friendshipRepository.searchFriendsByName(user, searchTerm);
        
        if (friends.isEmpty()) {
            return Collections.emptyList();
        }
        
        return friends.stream()
                .map(UserDTO::fromEntity)
                .collect(Collectors.toList());
    }

    @Override
    @Transactional(readOnly = true)
    @Cacheable(value = "mutualFriendSuggestions", key = "#userId")
    public List<UserDTO> findPossibleMutualFriends(Long userId) {
        log.debug("Finding possible mutual friends for user ID: {}", userId);
        
        User user = userRepository.findById(userId)
                .orElseThrow(() -> new UserNotFoundException(userId));
        
        List<User> potentialMutualFriends = friendshipRepository.findPossibleMutualFriends(user);
        
        if (potentialMutualFriends.isEmpty()) {
            return Collections.emptyList();
        }
        
        return potentialMutualFriends.stream()
                .map(UserDTO::fromEntity)
                .collect(Collectors.toList());
    }
} 